/**
  ******************************************************************
  * @file    main.c
  * @author  merlin
  * @version V1.0
  * @date    2022-5-24
  * @brief   OLED，WS2812B
  ******************************************************************
  * @attention
  * verimake 用于
  * 磁吸小车
  ******************************************************************
  */
#include <CH549_sdcc.h>  //ch549的头文件，其中定义了单片机的一些特殊功能寄存器
#include <CH549_OLED.h>     //其中有驱动屏幕使用的函数
#include <CH549_BMP.h>      //用于显示图片的头文件
#include <CH549_DEBUG.h>    //CH549官方提供库的头文件，定义了一些关于主频，延时，串口设置，看门口，赋值设置等基础函数
#include <CH549_SPI.h>      //CH549官方提供库的头文件，定义了一些关于SPI初始化，传输数据等函数

 /********************************************************************
* TIPS:
*   oled引脚接线表
*   CS     P1_4 //CS片选
*   RST    P3_5 //LED复位
*   DC     P2_7 //数据/命令控制
*   D0     P1_7 //SCLK时钟信号 
*   D1     P1_5 //MOSI数据
*********************************************************************/
#define WS2812 P2_2                           //设定p2.2口作为灯带的输入口
#define key1 P3_2     //将单片机的P3.2端口定义为key1
#define _nop() __asm NOP __endasm             //将nop指令定义为宏

//1码，高电平850ns 低电平400ns 误差正负150ns
#define RGB_1() do{WS2812 = 1;\
    _nop();_nop();_nop();_nop();_nop();_nop();_nop();_nop();_nop();_nop();\
    _nop();_nop();_nop();_nop();_nop();_nop();_nop();_nop();_nop();_nop();\
    _nop();_nop();_nop();_nop();_nop();_nop();_nop();_nop();_nop();_nop();\
    _nop();_nop();_nop();_nop();_nop();_nop();_nop();_nop();_nop();_nop();\
	WS2812 = 0;}while(0)                                                               //此处加do while循环可以将宏定义的部分可以被识别为语句，方便纠错

//0码，高电平400ns 低电平850ns 误差正负150ns
#define RGB_0() do{WS2812 = 1;\
		_nop();_nop();_nop();_nop();_nop();_nop();_nop();_nop();_nop();_nop();\
        _nop();_nop();_nop();_nop();_nop();_nop();_nop();_nop();_nop();_nop();\
		WS2812 = 0;\
		_nop();_nop();_nop();_nop();_nop();_nop();_nop();_nop();_nop();_nop();\
		_nop();_nop();_nop();_nop();_nop();}while(0)

#define numLEDs 11   //灯的个数

enum roll_frame
{
    FRAME_5,
    FRAME_64,
    FRAME_128,
    FRAME_256,
    FRAME_3,
    FRAME_4,
    FRAME_25,
    FRAME_2,
};

enum page_num
{
    PAGE0,
    PAGE1,
    PAGE2,
    PAGE3,
    PAGE4,
    PAGE5,
    PAGE6,
    PAGE7,
};

//发送24位数据
void Send_2811_24bits(unsigned char G8,unsigned char R8,unsigned char B8)
{
	  
	  char n = 0;
	  //发送G8位
		for(n=0;n<8;n++)
		{
			
			if(G8&0x80)
			{
				RGB_1();
			}
			else  
			{
			  RGB_0();
			}
      G8<<=1;
		}
		//发送R8位
		for(n=0;n<8;n++)
		{
			
			if(R8&0x80)
			{
				RGB_1();
			}
			else  
			{
				RGB_0();
			}
			R8<<=1;
		}
		//发送B8位
	  for(n=0;n<8;n++)
		{
		
			if(B8&0x80)
			{
				RGB_1();
			}
			else  
			{
			  RGB_0();
			}
      	B8<<=1;
		}
}

//复位码
void RGB_Rst()
{
		WS2812 = 0;
		mDelayuS( 50 );
}

//直接将颜色数据赋予灯带
void Set_Light(unsigned char x,unsigned char y,unsigned char z)
{
unsigned char i;
for ( i = 0; i < numLEDs; i++)
{
Send_2811_24bits( x , y , z );//发送显示
}
}

/******************************************************************************************************
 * @brief       OLED水平滚动显示
 * @param       start_page  : PAGE0~PAGE7
 * @param       end_page  :  PAGE0~PAGE7    (end_page必须大于start_page)
 * @param       frame  : 0~7
 * @param       dir  : 0 右滚   1 左滚
 * @retval      无
 *******************************************************************************************************/
void oled_hor_scroll(enum page_num start_page, enum page_num end_page, enum roll_frame frame, UINT8 dir)
{
    OLED_WR_Byte(0x2E, OLED_CMD);                   /* 停止滚动 调用后,RAM数据需要重新写入 */
    
    OLED_WR_Byte(dir ? 0x26 : 0x27, OLED_CMD);      /* 0,向右滚动 1,向左移动 */
    OLED_WR_Byte(0x00, OLED_CMD);                   /* 发送空字节 设置为0x00即可 */
    OLED_WR_Byte(start_page & 0x07, OLED_CMD);      /* 起始页地址 */
    OLED_WR_Byte(frame & 0x07, OLED_CMD);           /* 设置滚动步长的时间间隔, 帧为单位 */
    OLED_WR_Byte(end_page & 0x07, OLED_CMD);        /* 终止页地址 */

    OLED_WR_Byte(0x00, OLED_CMD);   /* 发送空字节 设置为0x00即可 */
    OLED_WR_Byte(0xFF, OLED_CMD);   /* 发送空字节 设置为0xFF即可 */
    OLED_WR_Byte(0x2F, OLED_CMD);   /* 启动滚动 禁止对RAM访问,改变水平滚动配置参数 */
}

/********************************************************************
* 函 数 名         : Int0Init()
* 函数功能		     : 设置外部中断0
* 输    入         : 无
* 输    出         : 无
*********************************************************************/
void Int0Init()
{
	//设置INT0
	IT0=1;  //跳变沿触发方式（下降沿）
	EX0=1;  //打开INT0的中断允许。	
	EA=1;   //打开总中断	
}

volatile UINT8 F;        //水平滚动指令需要无限循环，给与屏幕赋值会干扰水平滚动，设定F，仅在第一次给屏幕赋值，便置0跳过赋值指令
volatile UINT8 led = 0;  //定义灯的四种情况

/********************************************************************
* 函 数 名       : main
* 函数功能		 : 主函数
* 输    入       : 无
* 输    出    	 : 无
*********************************************************************/
void main(void)
{
  CfgFsys(); //CH549时钟选择配置
  mDelaymS(20);
  Int0Init();                                                                //对外部中断0（P3.2）初始化
  SPIMasterModeSet(3);                                                       //SPI主机模式设置，模式3
  SPI_CK_SET(12);                                                            //设置spi sclk 时钟信号为12分频
  OLED_Init();			                                                         //初始化OLED  
  OLED_Clear();                                                              //将oled屏幕上内容清除
  setFontSize(16);                                                           //设置文字大小
  F = 1;
  /* 主循环 */
  while (1)
  {
      switch (led)
  {
  case 0:if(F == 1) {F=0;OLED_Clear();OLED_ShowString(0,2,"RESTING");}           //接电后初始为熄灭状态，且OLED上显示为RESTING
         Set_Light( 0X00 , 0X00 , 0X00 );mDelaymS(30);break;
  case 1:if(F == 1) {F=0;OLED_Clear();OLED_ShowString(0,2,"BACK OFF");}          //按第一下按钮为红灯，且OLED上显示为BACK OFF
         Set_Light( 0X00 , 0Xff , 0X00 );mDelaymS(30);break;
  case 2:if(F == 1) {F=0;OLED_Clear();OLED_ShowString(0,2,"KNOCK KNOCK");}       //按第二下按钮为黄灯，且OLED上显示为KNOCK KNOCK   
         Set_Light( 0Xff , 0Xff , 0X00 );mDelaymS(30);break;
  case 3:if(F == 1) {F=0;OLED_Clear();OLED_ShowString(0,2,"FREEDOM!!");}         //按第三下按钮为绿灯，且OLED上显示为FREEDOM!!
         Set_Light( 0Xff , 0X00 , 0X00 );mDelaymS(30);break;
   default:led = 0;if(F == 1) {F=0;OLED_Clear();OLED_ShowString(0,2,"RESTING");} //按第四下按钮为灯熄灭，且OLED上显示为RESTING，相当于回到循环起点重新开始
         Set_Light( 0X00 , 0X00 , 0X00 );mDelaymS(30);break;
  } 
    oled_hor_scroll(PAGE0, PAGE7, FRAME_5, 0);    //OLED上的句子水平滚动
    delay_ms(500);
  }
}

/*******************************************************************
* 函 数 名         : Int0()	__interrupt 0
* 函数功能		     : 外部中断0的中断函数
* 输    入         : 无
* 输    出         : 无
* TIPS：1.Int0 可以修改成任意函数名
*       2.举例：外部中断1 void Int1()	__interrupt 2	
*       3.外部中断0       中断号0
*         定时器0中断     中断号1
*         外部中断1       中断号2
*         定时器1中断     中断号3
*******************************************************************/
void Int0()	__interrupt 0		//外部中断0的中断函数
{	
	delay_ms(120);	 //延时消抖
  IE0=0;
	if(key1==0)
	{
		led++;    //LED计数加1
    F = 1;    //按下按钮便重置给屏幕赋值的指令
	}
}